iT邦幫忙

1

從 Linux 基礎實現 Docker Bridge 網路:一步步理解容器通訊 (2)

  • 分享至 

  • xImage
  •  

部落格好讀版


前言

在上一章提到,安裝 Docker 後,Docker 會自動修改 iptables 和 NAT 規則,這可能導致我們手動配置的網路連線出現衝突或無法正常運作的情況。接下來我們來驗證這一點。

安裝 Docker 後再驗證

現在安裝完 Docker 後,我們進行 Ping 測試,檢查網絡連線是否正常:

# 從 ns1 ping 位於 ns0 的 veth0 IP
$ sudo ip netns exec ns1 ping -c 2 172.18.0.2
###
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
^C
--- 172.18.0.2 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1010ms
# 從 Host ping 位於 ns0 的 veth0 IP
$ ping -c 2 172.18.0.2
###
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
^C
--- 172.18.0.2 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1012ms

從結果來看,我們被拒於門外,驗證了 Docker 的安裝修改了 iptables。

iptables 是什麼

iptables 是一個用來管理網路封包過濾和 NAT 的工具,基於 Linux 內核的 netfilter 模組運作。簡單來說,它可以控制哪些網路流量被允許或阻擋,並處理地址轉換 (NAT)。其主要概念包括以下幾個部分:

  1. 表(Tables)

    • filter:管理封包的允許或阻擋(預設的處理)。
    • nat:進行地址轉換,適用於封包的來源和目的地址變更(如 SNAT、DNAT)。
    • mangle:對封包內容進行修改,通常用於特殊處理(如變更 TOS)。
    • raw:設定跳過連線追蹤,用於特定封包不進行追蹤。
  2. 鏈(Chains)

    • INPUT:進入本機的封包。
    • OUTPUT:從本機發出的封包。
    • FORWARD:路由轉發的封包。
    • PREROUTING:處理到達網卡前的封包。
    • POSTROUTING:處理即將發出的封包。
  3. 規則(Rules)
    每條規則定義匹配條件(如來源 IP、目的端口)及動作(如 ACCEPT、DROP、LOG)。

  4. 匹配(Match)與目標(Target)

    • 匹配條件:依據協議、IP、端口等過濾封包。
    • 目標動作:指定如何處理匹配的封包。

它運作的流程如下:


iptables 內建各表格與鏈的相關性 - 鳥哥

mangle 這個表很少用,省略它進一步精簡如下:


iptables 內建各表格與鏈的相關性(簡圖) - 鳥哥

filter table 的變化

還記得上一章中,我們準備了 network-test-1, network-test-2 兩台機器,我們只在前者安裝 Docker。下面我們將用這兩台機器查詢 iptable 資料,作為對照組,這樣能更清楚地了解 Docker 對網絡環境的影響。

使用以下指令查詢 iptable:

# 使用 -t flag 可以指定 table, 預設是 filter
sudo iptables -L --line-number

安裝 Docker 前,執行 iptables 結果如下:

Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination

安裝 Docker 後,執行 iptables 結果如下:

Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination

Chain FORWARD (policy DROP)
num  target     prot opt source               destination
1    DOCKER-USER  all  --  anywhere             anywhere
2    DOCKER-ISOLATION-STAGE-1  all  --  anywhere             anywhere
3    ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
4    DOCKER     all  --  anywhere             anywhere
5    ACCEPT     all  --  anywhere             anywhere
6    ACCEPT     all  --  anywhere             anywhere

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination

Chain DOCKER (1 references)
num  target     prot opt source               destination

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
num  target     prot opt source               destination
1    DOCKER-ISOLATION-STAGE-2  all  --  anywhere             anywhere
2    RETURN     all  --  anywhere             anywhere

Chain DOCKER-ISOLATION-STAGE-2 (1 references)
num  target     prot opt source               destination
1    DROP       all  --  anywhere             anywhere
2    RETURN     all  --  anywhere             anywhere

Chain DOCKER-USER (1 references)
num  target     prot opt source               destination
1    RETURN     all  --  anywhere             anywhere

iptables -L 指令省略了不少細節,讓我們改用 iptables-save 指令,可以呈現較完整的資訊:

# iptables-save 預設會將所有 table 匯出,所以要使用 -t flag 過濾 filter table
sudo iptables-save -t filter

安裝 Docker 前,執行 iptables-save 結果如下:

# Generated by iptables-save v1.8.8 (nf_tables) on Tue Nov 26 11:16:39 2024
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
# Completed on Tue Nov 26 11:16:39 2024

安裝 Docker 後,執行 iptables-save 結果如下:

# Generated by iptables-save v1.8.8 (nf_tables) on Tue Nov 26 11:13:50 2024
*filter
:INPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
COMMIT
# Completed on Tue Nov 26 11:13:50 2024

分析

在安裝 Docker 之前和之後,iptables 的主要變化如下:

  1. 安裝 Docker 後,FORWARD 鏈的策略由 ACCEPT 變為 DROP,強化了對容器之間和外部的網絡流量控制。這樣的變化有效提升了隔離性和網絡安全性,但同時也可能導致某些應用程序的連接受阻,需要根據具體應用進行額外的配置。
  2. 增加了多個新鏈,如 DOCKERDOCKER-ISOLATION-STAGE-1DOCKER-USER 等,以管理 Docker 容器的網絡規則,這些新鏈幫助分離和控制容器之間的通信,確保不同應用之間的網絡隔離,減少潛在的安全風險。

接下來我們詳細分析這些變化:

安裝 Docker 前:

  • 三個預設鏈(INPUT、FORWARD、OUTPUT)均設為 ACCEPT,表示所有封包均允許通過。
  • 無其他自定義規則或鏈,屬於「默認開放」狀態。
  • 缺乏細粒度控制,沒有針對性過濾或封包管理。

安裝 Docker 後:

  1. FORWARD 鏈的策略從 ACCEPT 改為 DROP

    • 改變:強制封包必須匹配 Docker 規則才能被轉發。
    • 原因
      • 加強網路隔離,避免無限制的封包轉發。
      • 僅允許來自容器的相關流量進行通信。
  2. 新增 DOCKER

    • 改變:容器的網路規則集中在 DOCKER 鏈管理。
    • 原因
      • 簡化管理,動態添加和刪除容器相關規則。
      • 確保容器與宿主機或外部網路的正常通信。
  3. 新增 DOCKER-ISOLATION-STAGE-1STAGE-2

    • 改變:為不同的 bridge 網絡隔離流量。
    • 原因
      • 保證來自不同 Docker 網橋(如 docker0)的容器之間默認不互相通信。
      • 用於安全隔離,避免未經授權的內部通信。
  4. 新增 DOCKER-USER

    • 改變:在 FORWARD 前引入一層用戶自定義規則。
    • 原因
      • 提供靈活性,允許用戶配置自定義防火牆規則,且不影響 Docker 自身規則。
  5. 新增 Rule

    • FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
      確保容器發起的連線能接收外部回應流量。
    • FORWARD -o docker0 -j DOCKER
      將容器相關的流量傳遞到 DOCKER 鏈。
    • FORWARD -i docker0 ! -o docker0 -j ACCEPT
      允許容器發往外部的流量。
    • FORWARD -i docker0 -o docker0 -j ACCEPT
      允許同一網橋內部的容器通信。

FORWARD 鏈在修改後,已經變為預設 DROP,然而我們 ping 來源和目的介面是自建的 docker1,而不是安裝 Docker 時預設的 docker0,所以封包在這個階段被丟掉了。

修改 FORWARD 讓封包可以過橋

需要注意的是,將策略修改為 ACCEPT 可能會使所有的轉發流量自動被允許,這在安全性方面可能會帶來風險,特別是在公開網絡中使用時。建議在更改策略後,根據實際需求進一步增加更細緻的防火牆規則來保護網絡安全。

使用以下指令修改預設策略:

sudo iptables --policy FORWARD ACCEPT

查詢 iptables FORWARD 鏈的策略:

$ sudo iptables -L
###
[...]
Chain FORWARD (policy ACCEPT)
[...]

再次測試 bridge 是否暢通:

# 從 ns1 ping 位於 ns0 的 veth0 IP
$ sudo ip netns exec ns1 ping -c 2 172.18.0.2
###
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
64 bytes from 172.18.0.2: icmp_seq=1 ttl=127 time=0.061 ms
64 bytes from 172.18.0.2: icmp_seq=2 ttl=127 time=0.050 ms

--- 172.18.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1065ms
rtt min/avg/max/mdev = 0.050/0.055/0.061/0.005 ms

容器之間的連線已經暢通。

不過容器與 Host 的問題依舊:

# 從 Host ping 位於 ns0 的 veth0 IP
$ ping -c 2 172.18.0.2
###
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
^C
--- 172.18.0.2 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1004ms

如果是對外網的連線呢?

# 找到 Host 的 enX0 ip
$ ip -br addr
###
[...]
enX0             UP             172.31.39.53/20 metric 512 fe80::49d:2ff:fe88:529d/64
[...]
######
# 從 ns1 ping 位於 Host 的 enX0 ip
$ sudo ip netns exec ns1 ping -c 2 172.31.39.53
###
ping: connect: Network is unreachable
######
# 從 ns1 ping 位於外部的 www.google.com
$ sudo ip netns exec ns1 ping -c 2 www.google.com
###
ping: www.google.com: Name or service not known

看起來,容器內對容器外的連線還沒有導通。

觀察封包流向

在 Linux 中,我們可以使用 tcpdump 網路封包分析工具,用來捕捉網路介面的封包,進行觀察。這次我們先用它,觀察容器間的封包流向。

首先,分別準備好四組指令和終端,分別監控 veth0, veth0-br, veth1, veth1-br 四個網路介面:

# 在 ns0 監控 veth0 的 ICMP 封包
sudo ip netns exec ns0 tcpdump -i veth0 -nn icmp

# 在 host 監控 veth0-br 的 ICMP 封包
sudo tcpdump -i veth0-br -nn icmp

# 在 host 監控 veth1-br 的 ICMP 封包
sudo tcpdump -i veth1-br -nn icmp

# 在 ns1 監控 veth1 的 ICMP 封包
sudo ip netns exec ns1 tcpdump -i veth1 -nn icmp

我們開啟第五個終端,執行以下指令測試:

# 從 ns1 ping 位於 ns0 的 veth0 IP
sudo ip netns exec ns1 ping -c 1 172.18.0.2

結果如下:

我們透過錄下來的資訊和時間戳記,排序後可以歸納出,封包的走向是這樣的:

小結

本章我們驗證了 Docker 對於 iptables 的影響,並用偷吃步的方式讓容器間的封包可以正常傳遞,但依然沒有解決容器對外的封包傳遞問題。這部分的內容就留到下一章。我們下次見。

參考


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言